////////////////////////////////////////////////////////////////////////////////
//  
//  Copyright (c) 2010 Gabriel Montagné Láscaris-Comneno and Alberto
//  Brealey-Guzmán.
//  
//  Permission is hereby granted, free of charge, to any person obtaining a
//  copy of this software and associated documentation files (the "Software"),
//  to deal in the Software without restriction, including without limitation
//  the rights to use, copy, modify, merge, publish, distribute, sublicense,
//  and/or sell copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following conditions:
//  
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//  
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
//  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//  
////////////////////////////////////////////////////////////////////////////////

package com.rojored.controls 
{

import com.rojored.controls.dragSurfaceClasses.DragSurfaceEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.utils.getQualifiedClassName;
import mx.core.UIComponent;
import mx.events.FlexEvent;
import mx.logging.ILogger;
import mx.logging.Log;

//--------------------------------------
//   Events 
//--------------------------------------

/**
 *  Event dispatched when the horizontal index has changed
 */
[Event(name="horizontalIndexChanged", type="com.rojored.controls.dragSurfaceClasses.DragSurfaceEvent")]

/**
 *  Event dispatched when the vertical index has changed
 */
[Event(name="verticalIndexChanged", type="com.rojored.controls.dragSurfaceClasses.DragSurfaceEvent")]

/**
 *  Event dispatched when dragging has started.
 */
[Event(name="dragStart", type="com.rojored.controls.dragSurfaceClasses.DragSurfaceEvent")]

/**
 *  Event dispatched when dragging has stopped.
 */
[Event(name="dragStop", type="com.rojored.controls.dragSurfaceClasses.DragSurfaceEvent")]

/**
 *  Component for measuring dragging
 */
public class DragSurface extends UIComponent 
{

    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor
     */
    public function DragSurface() 
    {
        super();
        addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
        addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
        addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);
    }

    //--------------------------------------------------------------------------
    //
    //  Variables 
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    private var initialHorizontalIndex:Number;

    /**
     *  @private
     */
    private var initialVerticalIndex:Number;

    /**
     *  @private
     */
    private var isMouseDown:Boolean;

    /**
     *  @private
     */
    private var isMouseDownChanged:Boolean;

    /**
     *  @private
     */
    private var logger:ILogger =
        Log.getLogger(getQualifiedClassName(this).replace(/\W+/g, ".")); 

    /**
     *  @private
     */
    private var mouseMoved:Boolean;

    /**
     *  @private
     *  x position of the mouse when the drag started.
     */
    private var mouseXStart:Number;

    /**
     *  @private
     *  y position of the mouse when the drag started. 
     */
    private var mouseYStart:Number;

    /**
     *  @private
     *  Last tracked mouse position, on the x axis. 
     *
     *  Because we're using the validation / invalidation cycle, validation
     *  could come a frame or two after the mouse gesture has occurred; if we
     *  measure the mouse position then, it could be out of sync and out of
     *  range.
     */
    private var lastRecordedMouseX:Number;

    /**
     *  @private
     *  Last tracked mouse position, on the y axis. 
     *
     *  Because we're using the validation / invalidation cycle, validation
     *  could come a frame or two after the mouse gesture has occurred; if we
     *  measure the mouse position then, it could be out of sync and out of
     *  range.
     */
    private var lastRecordedMouseY:Number;

    //--------------------------------------------------------------------------
    //
    //  Properties 
    //
    //--------------------------------------------------------------------------

    //--------------------------------------
    //  active 
    //--------------------------------------

    [Bindable]

    /**
     *  if <code>true</code> index will respond to the surface drag
     */
    public var active:Boolean = true;

    //--------------------------------------
    //  debug 
    //--------------------------------------

    /**
     *  @private
     *  Storage for the debug property
     */
    private var _debug:Boolean;

    [Bindable("debugChanged")]

    /**
     *  if <code>true</code> the component will be tinted to see its position
     *  and size.
     */
    public function get debug():Boolean
    {
        return _debug;
    }

    /**
     *  @private
     */
    public function set debug(value:Boolean):void
    {
        if (_debug == value)
            return;

        _debug = value;
        invalidateDisplayList();
        dispatchEvent(new Event("debugChanged"));
    }

    //--------------------------------------
    //  incrementalHorizontalIndex 
    //--------------------------------------

    /**
     *  if <code>true</code>, the horizontal index accumulates and if you
     *  release and redrag the value won't reset but will be incremented.
     */
    public var incrementalHorizontalIndex:Boolean = true;

    //--------------------------------------
    //  incrementalVerticalIndex 
    //--------------------------------------

    /**
     *  if <code>true</code>, the vertical index accumulates and if you
     *  release and redrag the value won't reset but will be incremented.
     */
    public var incrementalVerticalIndex:Boolean = true;

    //--------------------------------------
    //  horizontalIndex 
    //--------------------------------------

    /**
     *  @private
     *  Storage for the horizontalIndex property
     */
    private var _horizontalIndex:Number = 0;

    /**
     *  @private
     */
    private var horizontalIndexChanged:Boolean;

    [Bindable("horizontalIndexChanged")]

    /**
     *  thre resuliting horizontal index after dragging.
     */
    public function get horizontalIndex():Number
    {
        return _horizontalIndex;
    }

    /**
     *  @private
     */
    public function set horizontalIndex(value:Number):void
    {
        if (_horizontalIndex == value)
            return;

        _horizontalIndex = value;
        horizontalIndexChanged = true;
        dispatchEvent(
            new DragSurfaceEvent(
                DragSurfaceEvent.HORIZONTAL_INDEX_CHANGED,
                horizontalIndex, 
                verticalIndex
            )
        );
        invalidateProperties();
    }

    //--------------------------------------
    //  verticalIndex 
    //--------------------------------------

    /**
     *  @private
     *  Storage for the verticalIndex property
     */
    private var _verticalIndex:Number = 0;

    /**
     *  @private
     */
    private var verticalIndexChanged:Boolean;

    [Bindable("verticalIndexChanged")]

    /**
     *  the resulting vertical index after dragging.
     */
    public function get verticalIndex():Number
    {
        return _verticalIndex;
    }

    /**
     *  @private
     */
    public function set verticalIndex(value:Number):void
    {
        if (_verticalIndex == value)
            return;

        _verticalIndex = value;
        verticalIndexChanged = true;
        dispatchEvent(
            new DragSurfaceEvent(
                DragSurfaceEvent.VERTICAL_INDEX_CHANGED,
                horizontalIndex,
                verticalIndex
            )
        );
        invalidateProperties();
    }

    //--------------------------------------
    //  verticalIndexFactor 
    //--------------------------------------

    /**
     *  multiplier for the vertical index distance per pixel.
     */
    public var verticalIndexFactor:Number = 1;

    //--------------------------------------
    //  horizontalIndexFactor 
    //--------------------------------------

    /**
     *  multiplier for the horizontal index distance per pixel.
     */
    public var horizontalIndexFactor:Number = 1;

    //--------------------------------------
    // isDragging 
    //--------------------------------------

    /**
     *  @private
     *  Storage for the isDragging property
     */
    private var _isDragging:Boolean;

    [Bindable("isDraggingChanged")]

    /**
     *  if <code>true</code> the user is currently dragging.
     */
    public function get isDragging():Boolean
    {
        return _isDragging;
    }

    //--------------------------------------------------------------------------
    //
    //  Overriden methods
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    override protected function measure():void
    {
        super.measure();
        measuredWidth = 300;
        measuredHeight = 300;
    }

    /**
     *  @private
     */
    override protected function updateDisplayList(unscaledWidth:Number,
                                                  unscaledHeight:Number):void
    {
        super.updateDisplayList(unscaledWidth, unscaledHeight);
        graphics.clear();
        graphics.beginFill(0xCCCCCC, debug ? 0.3 : 0);
        graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
        graphics.endFill();
    }

    //--------------------------------------------------------------------------
    //
    //  Overriden methods: UIComponent 
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    override protected function commitProperties():void
    {
        super.commitProperties();

        if (isMouseDownChanged)
        {
            isMouseDownChanged = false;
            if (isMouseDown)
                startDragging();
            else
                stopDragging();
        }

        if (horizontalIndexChanged)
        {
            horizontalIndexChanged = false;
            if (isDragging)
            {
                mouseXStart = lastRecordedMouseX;
                initialHorizontalIndex = horizontalIndex;
            }
        }

        if (verticalIndexChanged)
        {
            verticalIndexChanged = false;
            if (isDragging)
            {
                mouseYStart = lastRecordedMouseY;
                initialVerticalIndex = verticalIndex;
            }
        }

        if (mouseMoved && active)
        {
            mouseMoved = false;
            var newHorizontalIndex:Number = initialHorizontalIndex + 
                ((lastRecordedMouseX - mouseXStart) * horizontalIndexFactor);

            var newVerticalIndex:Number = initialVerticalIndex + 
                ((lastRecordedMouseY - mouseYStart) * verticalIndexFactor);

            if (newHorizontalIndex != _horizontalIndex)
            {
                _horizontalIndex = newHorizontalIndex;
                dispatchEvent(
                    new DragSurfaceEvent(
                        DragSurfaceEvent.HORIZONTAL_INDEX_CHANGED,
                        horizontalIndex,
                        verticalIndex
                    )
                );
            }

            if (newVerticalIndex != _verticalIndex)
            {
                _verticalIndex = newVerticalIndex;
                dispatchEvent(
                    new DragSurfaceEvent(
                        DragSurfaceEvent.VERTICAL_INDEX_CHANGED,
                        horizontalIndex,
                        verticalIndex
                    )
                );
            }
        }
    }

    //--------------------------------------------------------------------------
    //
    //  Methods 
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    private function startDragging():void
    {

        if (!isDragging)
            _isDragging = true;

        mouseXStart = lastRecordedMouseX;
        mouseYStart = lastRecordedMouseY;

        initialHorizontalIndex = incrementalHorizontalIndex ? 
            horizontalIndex : (lastRecordedMouseX * horizontalIndexFactor);

        horizontalIndex = initialHorizontalIndex;

        initialVerticalIndex = incrementalVerticalIndex ? 
            verticalIndex : (lastRecordedMouseY * verticalIndexFactor);

        verticalIndex = initialVerticalIndex;

        addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
        dispatchEvent(new Event("isDraggingChanged"));
        dispatchEvent(
            new DragSurfaceEvent(
                DragSurfaceEvent.DRAG_START,
                horizontalIndex,
                verticalIndex
            )
        );
        dispatchEvent(new FlexEvent(FlexEvent.CHANGE_START));
    }

    /**
     *  @private
     */
    private function stopDragging():void
    {
        if (isDragging)
            _isDragging = false;

        removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
        dispatchEvent(new Event("isDraggingChanged"));
        dispatchEvent(
            new DragSurfaceEvent(
                DragSurfaceEvent.DRAG_STOP,
                horizontalIndex,
                verticalIndex
            )
        );
        dispatchEvent(new FlexEvent(FlexEvent.CHANGE_END));
    }

    /**
     *  @private
     *
     *  @see #lastRecordedMouseX
     *  @see #lastRecordedMouseY
     */
    private function recordMousePosition():void
    {
        lastRecordedMouseX = mouseX;
        lastRecordedMouseY = mouseY;
    }

    //--------------------------------------------------------------------------
    //
    //  Event handlers 
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    private function mouseDownHandler(event:MouseEvent):void
    {
        if (isMouseDown)
            return;

        isMouseDown = true;
        isMouseDownChanged = true;
        recordMousePosition();
        invalidateProperties();

    }

    /**
     *  @private
     */
    private function mouseUpHandler(event:MouseEvent):void
    {
        if (!isMouseDown)
            return;

        isMouseDown = false;
        isMouseDownChanged = true;
        recordMousePosition();
        invalidateProperties();
    }

    /**
     *  @private
     */
    private function mouseOutHandler(event:MouseEvent):void
    {
        if (!isMouseDown)
            return;

        isMouseDown = false;
        isMouseDownChanged = true;
        recordMousePosition();
        invalidateProperties();
    }

    /**
     *  @private
     */
    private function mouseMoveHandler(event:MouseEvent):void
    {
        mouseMoved = true;
        recordMousePosition();
        invalidateProperties();
    }
}
}
